package seacher;

import java.io.*;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;


public class ParserTest {
    // 先指定的是加载文档的路径
    private static final String INPUT_PATH = "E:/Document java 8/docs/api";
    // 创建一个 index 实例
    private Index index = new Index();

    // 第三方库提供，线程安全有保障，长整型的原子性操作
    private AtomicLong t1 = new AtomicLong();
    private AtomicLong t2 = new AtomicLong();

    public void run() {
        long beg = System.currentTimeMillis();
        System.out.println("索引制作开始！");
        // Parser类 的执行入口
        // 1、根据上面指定的路径，需要枚举出路径中所有的文件（html），这个过程需要把所有子目录中的文件都获取到（递归）
        ArrayList<File> fileList = new ArrayList<>();
        enumFile(INPUT_PATH, fileList);
        long endEnumFile = System.currentTimeMillis();
        System.out.println("枚举文件完毕!,消耗时间：" + (endEnumFile - beg) + "ms");
//        System.out.println(fileList);
//        System.out.println(fileList.size());
        // 2、针对上面罗列出的文件路径，打开文件，读取文件内容，进行解析，并构建索引
        for (File f : fileList) {
            // 通过这个方法来解析单个的html文件
            System.out.println("开始解析：" + f.getAbsolutePath());
            parseHTML(f);
        }
        long endFor = System.currentTimeMillis();
        System.out.println("遍历文件完毕！消耗时间：" + (endFor - endEnumFile) + "ms");
        //  3、把在内存中构建好的索引数据结构，保存到指定文件中。
        index.saveDisk();
        long end = System.currentTimeMillis();
        System.out.println("索引制作完毕！消耗时间：" + (end - beg) + "ms");
    }

    // 这个方法实现“多线程索引制作”
    public void runByThread() throws InterruptedException {
        long begin = System.currentTimeMillis();
        System.out.println("索引制作开始！");

        // 1、枚举出所有的文件
        ArrayList<File> files = new ArrayList<>();
        enumFile(INPUT_PATH, files);

        // 2、循环遍历文件，此处为了能通过多线程制作索引，就直接引入线程池
        CountDownLatch latch = new CountDownLatch(files.size());
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        for (File f : files) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("开始解析：" + f.getAbsolutePath());
                    parseHTML(f);
                    latch.countDown();//撞线都通知一下
                }
            });
        }
        latch.await();// 选手都撞线完毕！
        // 3、保存索引
        index.saveDisk();
        // 手动把线程池里面的线程都干掉
        executorService.shutdown();

        long end = System.currentTimeMillis();
        System.out.println("索引制作完毕！消耗时间：" + (end - begin) + "ms");
        System.out.println("t1: " + t1 + ", t2: " + t2);
    }

    public void parseHTML(File f) {
        // 1、解析出 HTML 标题
        String title = parseTitile(f);
        // 2、解析出 HTML 对应的 URL
        String url = parseUrl(f);
        // 3、解析出 HTML 的正文（有了正文才有后面的描述）
        long beg = System.nanoTime();//纳秒
        String content = parseContent(f);
        long mid = System.nanoTime();
        // 4、把解析出来的信息，加如到索引当中
        index.addDoc(title, url, content);
        long end = System.nanoTime();
        t1.addAndGet(mid - beg);
        t2.addAndGet(end - mid);
    }

    public String parseContent(File f) {
        // 方法是使用一个字符一个字符的读取，设置flag，以 < 和 > 来控制拷贝数据的开关
        // file 文件不存在捕获异常
        // BufferedReader 他是 FileRead的辅助类，作用是它内部设置了缓存区，可以把FileRead东西提前放入缓存区中
        // 第二个参数是缓存区容量大小
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(f), 1024 * 10224)) { // 文件操作最后要关流，这样写自动执行关闭操作
            boolean isCopy = true; // 控制拷贝的开关
            StringBuilder content = new StringBuilder(); // 拷贝的内容保存在 StringBulider里面，线程不安全执行的效率会高
            while (true) {
                // 此处的 read()返回值是一个 int 类型，而不是 char
                // 此处的使用 int 作为返回值，主要是为了表示一些非法的情况
                // 如果文件读到了末尾，继续读，就会返回 -1
                int ret = bufferedReader.read(); // 输入输出的时候异常
                if (ret == -1) {
                    break; // 文件读到了末尾，读完了
                }
                char c = (char) ret; // 结果不是 -1 ，就是一个合理的字符，转换char 继续读
                // \r 是 回车符，保险起见都要加判断
                if (c == '\n' || c == '\r') {
                    c = ' ';
                }
                if (isCopy) {
                    if (c == '<') {
                        isCopy = false;
                        continue; // 跳过这次循环，进入下一次循环
                    }
                    content.append(c); // 其他字符可以直接拷贝到 StringBulider容器里面

                } else {
                    if (c == '>') {
                        // 遇到这个符号了就可以 打开开关了，
                        isCopy = true;
                    }
                }

            }
            return content.toString();
        } catch (IOException e) {
            // 都是 IOE 的子类可以合并
            e.printStackTrace();
        }

        return "";
    }

    public String parserContentByRegex(File f) {
        // 1、先把整个文档读到 String 里面
        String content = readFile(f);
        // 2、替换到 script 标签
        // replaceAll(正则表达式，替换内容）
        content = content.replaceAll("<script.*?>(.*?)</script>", " ");
        // 3. 替换掉普通的 html 标签
        content = content.replaceAll("<.*?>", " ");
        // 2,3的顺序不要写反了！！！
        // 4、使用正则把多个空格, 合并成一个空格,别忘了“+”符号，“+”至少保证有一个空格，“*”没有也可以匹
        content = content.replaceAll("\\s+", " ");
        return content;
    }

    private String readFile(File f) {
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(f))) {
            StringBuilder content = new StringBuilder();
            while (true) {
                int ret = bufferedReader.read();
                if (ret == -1) {
                    break;
                }
                char c = (char) ret;
                if (c == '\n' || c == '\r') {
                    c = ' ';
                }
                content.append(c);
            }
            return content.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

    public String parseUrl(File f) {
        // 先获取到固定前缀
        String part1 = "https://docs.oracle.com/javase/8/docs/api/";
        String part2 = f.getAbsolutePath().substring(INPUT_PATH.length());
        return part1 + part2;
    }

    private String parseTitile(File f) {
        String name = f.getName();
        return name.substring(0, name.length() - ".html".length());
    }

    // 第一个参数表示从那个路径开始进行递归遍历
    // 第二个参数表示递归得到的结果
    private void enumFile(String inputPath, ArrayList<File> fileList) {
        File rootPath = new File(inputPath); // 因为 inputPath 是字符串需要转换成 File文件对象
        // listFiles() 能获取到 rootPath 当前目录下所包含的文件/目录
        // 但是 listFiles（） 只能看见一级目录，看不到子目录里面的内容，要看到子目录内容就需要递归
        File[] files = rootPath.listFiles();
        for (File f : files) {
            // 根据当前 f 的类型决定是否递归
            if (f.isDirectory()) {
                enumFile(f.getAbsolutePath(), fileList);
            } else {
                if (f.getAbsolutePath().endsWith(".html")) {
                    fileList.add(f);
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 制作索引的过程
        ParserTest parser = new ParserTest();
        parser.runByThread();
    }
}
